package org.etnaframework.module.web.api;

import org.springframework.core.Ordered;
import org.springframework.lang.NonNull;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Optional;

/**
 * 用于记录日志、异常处理等操作的通用过滤器
 *
 * @author jasyaf
 * @since 2023-12-27
 */
public class WebCommonFilter extends OncePerRequestFilter implements Ordered {

    public static final String FORWARD_REQUEST_ATTRIBUTE = WebCommonFilter.class.getName() + "->Request";

    private final IWebCommonCustomizer commonCustomizer;

    private final WebAccessLog webAccessLog;

    public WebCommonFilter(IWebCommonCustomizer commonCustomizer, IWebAccessLogCustomizer accessLogCustomizer) {
        this.commonCustomizer = null != commonCustomizer ? commonCustomizer : new DefaultWebCommonCustomizer();
        this.webAccessLog = new WebAccessLog(null != accessLogCustomizer ? accessLogCustomizer : new DefaultWebAccessLogCustomizer());
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE; // 配置最高优先级，只有这样才能保证记录到所有请求的日志、捕获到所有Filter中抛出的异常
    }

    @Override
    protected boolean shouldNotFilterErrorDispatch() {
        return false; // 覆盖该方法并返回false，才能处理404 -> errorPage
    }

    @Override
    protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException {
        long startTime = System.currentTimeMillis();
        try {
            if (!(request instanceof ContentCachingRequestWrapperPlus)) {
                request = new ContentCachingRequestWrapperPlus(request);
            }
            if (!(response instanceof ContentCachingResponseWrapper)) {
                response = new ContentCachingResponseWrapper(response);
            }
            commonCustomizer.beforeRequest(request, response);
            filterChain.doFilter(request, response);
        } catch (Throwable ex) {
            commonCustomizer.error(request, response, ex);
        } finally {
            // 如果是404会产生2条记录，第1条没有response内容，第2条是请求被导向/error的请求且有response内容，但从客户端的角度来看只有一次请求
            // 这里通过判断response.isCommitted()把第1条过滤掉，并记录请求相关信息到request.attribute，供第2条的提取生成请求日志
            if (response.isCommitted()) {
                request.setAttribute(FORWARD_REQUEST_ATTRIBUTE, request);
            } else {
                String path = Optional.ofNullable(request.getAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE)).map(Object::toString).orElse(request.getRequestURI());
                HttpServletRequest req = Optional.ofNullable((HttpServletRequest) request.getAttribute(FORWARD_REQUEST_ATTRIBUTE)).orElse(request);
                webAccessLog.save(req, response, path, startTime);
            }
            ContentCachingResponseWrapper responseWrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
            if (null != responseWrapper) {
                responseWrapper.copyBodyToResponse();
            }
            commonCustomizer.afterRequest(request, response);
        }
    }

    public IWebCommonCustomizer getCommonCustomizer() {
        return commonCustomizer;
    }

    public class ContentCachingRequestWrapperPlus extends ContentCachingRequestWrapper {

        public ContentCachingRequestWrapperPlus(HttpServletRequest request) {
            super(request);
        }

        private String remoteAddr;

        private String scheme;

        /**
         * 获取请求来源IP，获取规则可在{@link IWebCommonCustomizer#getRemoteAddr(HttpServletRequest)}自定义
         */
        @Override
        public String getRemoteAddr() {
            if (null == remoteAddr) {
                remoteAddr = commonCustomizer.getRemoteAddr((HttpServletRequest) getRequest());
            }
            return remoteAddr;
        }

        /**
         * 获取请求的scheme(http/https)，获取规则可在{@link IWebCommonCustomizer#getScheme(HttpServletRequest)}自定义
         */
        @Override
        public String getScheme() {
            if (null == scheme) {
                scheme = commonCustomizer.getScheme((HttpServletRequest) getRequest());
            }
            return scheme;
        }
    }
}
